package gbench.sandbox.record.cph;

import static java.util.Arrays.asList;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.*;
import org.junit.jupiter.api.Test;

import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.tree.LittleTree.TrieNode;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.*;
import static gbench.common.tree.LittleTree.IRecord.*;


/**
 * 全排列的实现
 * 
 * @author gbench
 *
 */
public class JunitCph {
    
    /**
     * 这是一个分镜头脚本
     */
    @Test
    public void bar() {// 镜头语言
        final var t = cph2("远中近特".split(""),"推拉跟摇移".split(""),"远中近特".split(""))
        .filter(e->!e.get(0).equals(e.get(2)))
        .collect(rclc);
        System.out.println(t.keys("景别1,镜头运用,景别2").toString2());
    }
    
    /**
     * 这是一个模拟SQL数据JOIN的数据连接。
     */
    @Test
    public void foo() {// 镜头语言
        final var rec = cph2(// 模拟构造数据库的join 连接
            A(STRING2REC("1/2/3"),STRING2REC("a/b/c")),// table1 的行记录集合
            A(STRING2REC("1/2/3"),STRING2REC("a/b/c")) // table2 的行记录集合
        ).collect(rclc);// rec 是一个只有两列数据。
        System.out.println(rec.toString2());// 注意这里是两个列数据而不是6列
        
        System.out.println("数据扁平化");
        final var t = rec.rows().stream()
        .map(e-> e.rec(0).keys(k->"a_"+k).add(e.rec(1).keys(k->"b_"+k)))
        .collect(rclc);
        System.out.println(t.toString2());
    }
    
    /**
     * 这是一个模拟SQL数据JOIN的数据连接。
     */
    @Test
    public void foo2() {// 镜头语言
        // 模拟构造数据库的join 连接
        final var rec = cph2(L(
            LIST(Stream.of("1/2/3","4/5/6").map(IRecord::STRING2REC)), // 表b的记录集合
            LIST(Stream.of("1/2/3","4/5/6").map(IRecord::STRING2REC)) // 表a的记录集合
        )).map(e-> e.rec(0).keys(k->"a_"+k).add(e.rec(1).keys(k->"b_"+k)))
        .collect(rclc);// rec 是一个只有两列数据。
        System.out.println(rec.toString2());// 注意这里是两个列数据而不是6列
    }
    
    /**
     * 这是一个模拟SQL数据JOIN的数据连接。
     */
    @Test
    public void foo3() {// 镜头语言
        
        // 模拟构造数据库的join 连接
        final var rec = cph2(
            LIST(Stream.of("1/2/3","4/5/6").map(IRecord::STRING2REC)),// 表b
            LIST(Stream.of("1/2/3","4/5/6","7/8/9","11/12/13").map(IRecord::STRING2REC)) // 表a
        ).map(e-> e.rec(0).keys(k->"a_"+k).add(e.rec(1).keys(k->"b_"+k)))
        .collect(rclc);// rec 是一个只有两列数据。
        System.out.println(rec.toString2());// 注意这里是两个列数据而不是6列
        
    }
    
    /**
     * 这是一个模拟SQL数据JOIN的数据连接。
     * select a_0,b_1 from a,b where a_0==b_0 的模拟
     */
    @Test
    public void qux() {// 镜头语言
        
        // 模拟构造数据库的join 连接
        final var rec = cph2(
            LIST(Stream.of("1/2/3","4/5/6").map(IRecord::STRING2REC)),// 表b
            LIST(Stream.of("1/2/3","4/5/6").map(IRecord::STRING2REC)) // 表a
        ).map(e-> e.rec(0).keys(k->"a_"+k).add(e.rec(1).keys(k->"b_"+k)))
        .filter(e->e.get("a_0").equals(e.get("b_0")))// where a_0==b_0
        .map(e->e.filter("a_0,b_1"))// select a_0,b_1列
        .collect(rclc);// rec 是一个只有两列数据。
        System.out.println(rec.toString2());// 注意这里是两个列数据而不是6列
        
        rec.columns2().forEach(col->{
            System.out.println(col);
        });
        
    }
    
    /**
     * 采用 TrieNode 节点 来 模拟一个 全排列 <br>
     * TrieNode 节点
     * 
     * @param <T>     数据元素
     * @param batches 批次层级数据,[batch1,batch2,...,batch_i,...] 每个批次 batch_i 都是一个 [t]
     * @return TrieNode 节点
     */
    @SafeVarargs
    final public <T> TrieNode<T> cph(final T[]... batches) {
        final var trie = TrieNode.of((T) null); // 创建根节点
        // 需要注意queue是一个逐渐增大的数据队列。也就是说 这不是一个 收敛的 而是 一个发散的 过程。
        final Queue<TrieNode<T>> queue = new LinkedList<TrieNode<T>>(Collections.singleton(trie)); // 存放批次处理的中间结果的 队列.
        for (final var batch : batches) { // 分批次添加数据
            int n = queue.size(); // 获取 批次 规模
            while (n-- > 0) {
                final var node = queue.poll(); // 提取 队列节点
                Stream.of(batch).map(node::add).forEach(queue::add); // 把batch处理的结果放入 批次队列之中（中间结果）
            } // while
        } // for
        return trie;
    };
    
    @Test
    public void foo4() {
        cph(RPTA(5, A(1, 2, 3, 4, 5d, 6, 7, 8, 9))) // 数据流化
        .filter(TrieNode::isLeaf) // 提取叶子节点
        // .filter(e->e.parts().size()==3)
        .forEach(e -> {
            println(e.getLevel(), e.getPathCode(), e.collect(XVec.xveclc(true)).cdrX());
        });
    }
    
    /**
     * array 形式的cph 结构
     * 
     * @param <T>
     * @param tts 批次层级数据,[tt1,tt2,...,tt_i,...] 每个批次 tt_i 都是一个 [t]
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> Stream<T[]> cpha(T[]... tts) {
        final var ai = new AtomicInteger(0); // 阶层偏移
        final var tclass = tts.getClass().getComponentType().getComponentType(); // tt 的元素的类型
        final var buffer = (T[]) Array.newInstance(tclass, tts.length); // 工作缓存,从根节点向叶子节点逐层递进

        // Stream.of((T) null) // 表示只有一个元素的流，与 Stream.empty 是不一样的，为了
        // 启动下游downstream的树形结构的展开活动，根节点有只有一个元素的流, 指至于这个元素内容是什么无所谓,所以这里使用了null
        // [null] flatMap [tt0] flatMap [tt1] ... 
        // flatMap 就像一个 一个连接器 把 tts 一段一段的连接起来了。这就是 reduce 的函数 的 累加效应
        return Stream.of(tts).reduce(Stream.of((T) null), (parentS, tt) -> {
            final var i = ai.getAndIncrement(); // level 阶层位置
            return parentS.flatMap(parent -> Stream.of(tt).map(t -> buffer[i] = t)); // 反腐刷新工作缓存输出流
        }, (a, b) -> a).map(e -> buffer); // 输出缓存状态
    }

    @Test
    public void foo5() {
        cpha(RPTA(10, A(0,1))) // 数据流化
        .forEach(e -> {
            println(e);
        });
    }
    
    /**
     * 全排列
     * 
     * @param <T> 排列元素 类型
     * @param tt  排列元素数组
     * @param len 排列长度
     * @return 排列结果的流
     */
    public <T> Stream<T[]> permute(final T[] tt, Integer len) {
        final var n = (len == null || len > tt.length ? tt.length : len);
        @SuppressWarnings("unchecked")
        final var buffer = (T[]) Array.newInstance(tt.getClass().getComponentType(), tt.length);

        return n == 0 // 排列长度
            ? Stream.empty() // 空数据
            // 生成树的展开，注意这里把reduce作为一个阶层的 过程结构 来使用, <br>
            // 流的lazy compute 使得map, flatMap 这样的运算变成了 一种层级的结构的构造运算，于是就可以利用这个特性来做 展开树的操作。
            // reduce 的反复 flatMap 就有点类似于道生一，一生二，二生三，三生万物的意思。 
            // 所以 reduce+flatMap 的本质就是一种过程的从一生多的结构（树形结构），这是一个有趣的思想，请注意领悟,并在实践中 给予丰富加强
            // 并采用元素数据外置到buffer之中的共享内存的方式对流进行操作,reduce
            : Stream.iterate(0, i -> i < n, i -> ++i).reduce(Stream.of(tt), // 生成树的初生代元素
                (oneS, i) -> { // ones 当前需要写入的元素, 也就是道生一的那个一，这个一的功能就是用来flatMap去生成更多的新的 ones,也及时一生二，生万物的的意思，
                    return oneS.map(one -> buffer[i] = one) // 写入元素数据。
                        .flatMap(one -> { // 使用flatMap做生成树的展开，flatMap 的特长就是善于从1点出发开始发散，于是就极为方便的用来做生成树。
                            final var ancestors = asList(Arrays.copyOfRange(buffer, 0, i + 1)); // 已经写入的祖先数据
                            final var _oneS = Stream.of(tt).filter(t -> !ancestors.contains(t)); // 把尚未排序的其他元素，新的ones 投递到下一阶层执行。
                            // 投递到下一阶层执行。
                            return i == n - 1 // 已经写入完成。
                                ? Stream.of((T) null) // 执行结束
                                : _oneS; // 新的ones
                        }); // flatMap 在parent的基础之上给予继续展开
                }, (a, b) -> a).map(e -> Arrays.copyOfRange(buffer, 0, n)); // 把buffer 中数据给予拷贝输出
    }

    /**
     * 全排列
     */
    @Test
    public void foo6() {
        final var tt = "12345".split("");
        Stream.iterate(0, i -> i <= tt.length, i -> i + 1).forEach(n -> {
            permute(tt, n).map(kvp_int(1)).forEach(e -> { // 全排列的元素输出
                println(n, e._1(), Arrays.deepToString(e._2()));
            });
            println();
        }); // forEach

    }
    
    /**
     * 采用插入法进行全排列
     * 
     * @param <T> 符号元素的类型
     * @param tt 排列元素的 字母序列
     * @return 全排列
     */
    @SuppressWarnings("unchecked")
    public <T> Stream<LinkedList<T>> permute2(final T... tt) {
        final var ai = new AtomicInteger(0); // 阶层深度
        
        return Stream.of(tt).reduce(Stream.of(new LinkedList<T>()), (parentS, t) -> { // 阶层的树形展开
            final int len = ai.incrementAndGet(); // 本次的层级深度，parentS 的深度为 len-1

            return parentS.flatMap(parent -> { // parent 的深度为len-1
                while (parent.size() > len - 1) { // 剔除无效数据
                    parent.removeLast();
                } // while
                
                // 给予空间位置的展开，插空法，由于 parent 的深度为len-1 所以 parent尾部的索引位置是 len-1 这也是为何 i < len
                return Stream.iterate(0, i -> i < len, i -> i+1).map(i -> { // i 是新元素的可能的插入的位置
                    if (parent.size() == len) { // 同一层内的位置替换
                        parent.remove(i - 1); // 去除掉上一次的变换结果，
                    } else if (parent.size() > len) { // 发生了阶层跃迁
                        /**
                         * 比如:从刚把3层 i-1# 的所有子节点即第4层的数据处理完后,准备继续处理3层的后续节点即i#节点。
                         * 此时需要明白,3层的i#尚未配置，因为只配置了i-1#(刚处理完毕嘛),
                         * 所以此时需要先清除先前i-1#的配置状态（已经失效）即 parent.remove(i-1) ，<br>
                         * 注意由于阶层跃迁（从叶子节点向根节点回归）i值的意义也不一样，他不再是接阶层内的上次配置，<br>
                         * 而是阶层间的上次配置（已经失效） parent.remove(i-1) 也就是把失效配置给予剔除的意思。
                         * 而后才能再按照1#的配置要求给予配置。
                         * 同时阶层间跃迁的时候，不要忘了与同一层内的位置替换一样 去除掉上一次的 变换结果，
                         * 但这一次 上一次的变换结果存放在了last位置，所以 parent.removeLast();
                         */
                        parent.remove(i - 1); // 去除上一级的配置状态
                        parent.removeLast(); // 去除末位数据配置状态
                    }  // if
                    parent.add(i, t); // 在i插入字母符号
                    
                    return parent;
                }); // map
            }); // flatMap 树形结构展开
        }, (a, b) -> a);
    }
    
    @Test
    public void foo7() {
        permute2(1, 2, 3, 4, 5).map(kvp_int(1)).forEach(e -> {
            println(e);
        });
    }

}
